接續實作-商品系統(六)-修改訂單部分(二),我們來完成商品系統的最後一個功能確認訂單
。
$ npm install nodemailer
新增的部分:
.
├── app.js
├── bin
│ └── www
├── config
│ └── development_config.js
├── controllers
│ ├── member
│ │ └── modify_controller.js
│ ├── order
│ │ ├── get_controller.js
│ │ └── modify_controller.js
│ ├── product
│ │ └── get_controller.js
├── models
│ ├── member
│ │ ├── encryption_model.js
│ │ ├── login_model.js
│ │ ├── register_model.js
│ │ ├── update_model.js
│ │ └── verifyication_model.js
│ ├── order
│ │ ├── all_order_model.js
│ │ ├── complete_model.js
│ │ ├── delete_model.js
│ │ ├── one_order_model.js
│ │ ├── order_all_product_model.js
│ │ ├── order_one_product_model.js
│ │ └── update_model.js
│ ├── product
│ │ └── all_product.js
│ ├── connection_mail.js
│ └── connection_db.js
├── package.json
├── public
│ ├── images
│ ├── javascripts
│ └── stylesheets
│ └── style.css
├── routes
│ ├── member.js
│ ├── order.js
│ └── product.js
├── sevice
│ └── member_check.js
├── views
├── error.ejs
└── index.ejs
├── .env
└── .gitignore
由於取得訂單資料(GET)
部分已經完成,所以我們直接實作最後一個API確認訂單(PUT)
。
整個交易流程到這步驟就代表會員已經按下類似確定購物
的按鈕,等同於我們可以確定這筆交易已經完成。所以該做的事情有:
註記:這部分我們還尚未實作金流功能。
這部分我們一樣提取之前做好的判斷function來使用:
const checkOrderData = (orderID, memberID) => {
return new Promise((resolve, reject) => {
db.query('SELECT * FROM order_list WHERE order_id = ? AND member_id = ? ', [orderID, memberID], function (err, rows) {
if (rows[0] === undefined) {
resolve(false);
} else {
resolve(true);
}
})
})
}
用來確認訂單的欄位是order_list
table的is_complete
。為0就是還未完成,為1就是已經完成。
const checkOrderComplete = (orderID) => {
return new Promise((resolve, reject) => {
db.query('SELECT is_complete FROM order_list WHERE order_id = ?', orderID, function (err, rows) {
if (rows[0].is_complete === 1) {
resolve(false);
} else {
resolve(true);
}
})
})
}
由於在進行進行商品的庫存確認
前,我們要先提取訂單中的product_id
才能再去product
的table進行庫存確認。
const getOrderData = (orderID, memberID) => {
return new Promise((resolve, reject) => {
db.query('SELECT * FROM order_list WHERE order_id = ? AND member_id = ? ', [orderID, memberID], function (err, rows) {
resolve(rows);
})
})
}
在此步驟,我們可以做個判斷,若是有庫存則回傳true
,但若是沒有庫存就回傳哪個商品庫存不足,好已告知會員目前有哪個庫存是沒有的。
const checkOrderStock = (orderProductID, orderQuantity) => {
return new Promise((resolve, rejct) => {
db.query('SELECT * FROM product WHERE id = ?', orderProductID, function (err, rows) {
if (rows[0].quantity >= orderQuantity && rows[0].quantity !== 0) {
resolve(true)
} else {
resolve(rows[0].name + "庫存不足")
}
})
})
}
上述所提到的5. 若都還有剩餘商品,則進行庫存的扣除
、6. 將訂單狀態鎖定
及7. 寄送email
都將在這步驟直接進行處理。但在實作前,我們先設定好nodemailer
的設置環境。
為了能夠使用寄送Eamil
的功能。因我們這部分的寄送email是使用google的gmail服務,所以我們得先去google的我的帳戶
中將允許安全性較低的應用程式
的功能啟用,在設置成啟用後才能使用寄送Email
的功能。
還記得在實作-會員系統(一)-會員註冊(一)的文章中,我們所使用到的dotenv
的套件嗎?先得在.env
的檔案中輸入讀者自己的gmail帳號及密碼。接續到config
資料夾的development_config.js
檔案中寫入:
require('dotenv').config()
module.exports = {
mysql: {
host: process.env.HOST,
user: process.env.DATABASE_USER,
password: process.env.DATABASE_PASSWORD,
database: process.env.DATABASE
},
secret: process.env.MY_SECRET,
senderMail: {
user: process.env.SEND_MAIL_USER,
password: process.env.SEND_MAIL_PASSWORD
}
}
接著在models
資料夾的connection_mail.js
中寫入:
const config = require('../config/development_config');
const nodemailer = require('nodemailer');
module.exports = nodemailer.createTransport({
service: 'gmail',
auth: {
user: config.senderMail.user, //gmail account
pass: config.senderMail.password //gmail password
}
});
註記:關於寄件的部分功能會一並寫在
確認訂單部分
的實作中。
這樣我們設置nodemailer環境
的部分就完成了。接續回到確認訂單部分
的實作,到models
> order
資料夾的complete_model.js
檔案中來將上述的功能組合起來。
const config = require('../../config/development_config');
const db = require('../connection_db');
const transporter = require('../connection_mail');
module.exports = function orderComplete(orderID, memberID) {
let result = {};
return new Promise(async (resolve, reject) => {
const hasData = await checkOrderData(orderID, memberID);
const hasComplete = await checkOrderComplete(orderID);
if (hasData === false) {
result.status = "訂單完成失敗。"
result.err = "沒有該訂單資料!"
reject(result)
} else if (hasComplete === false) {
result.status = "訂單完成失敗。"
result.err = "該訂單已經完成。"
reject(result)
} else if (hasData === true && hasComplete === true) {
// 取得order_list的table資料
const orderData = await getOrderData(orderID, memberID);
// 提取商品id
const productID = orderData[0].product_id;
// 依序確認訂單中的商品是否有庫存
for (let key in orderData) {
const hasStock = await checkOrderStock(orderData[key].product_id, orderData[key].order_quantity);
if (hasStock !== true) {
result.status = "訂單完成失敗。"
result.err = hasStock
reject(result);
return;
}
}
// 將商品庫存扣除
await db.query('UPDATE product, order_list SET product.quantity = product.quantity - order_list.order_quantity WHERE order_list.product_id = product.id and order_list.order_id = ?;', orderID, function (err, rows) {
if (err) {
console.log(err);
result.status = "訂單完成失敗。"
result.err = "伺服器錯誤,請稍後在試!"
reject(result);
return;
}
})
// 將is_complete的訂單狀態改為1
await db.query('UPDATE order_list SET is_complete = 1 WHERE order_id = ?', orderID, function (err, rows) {
if (err) {
console.log(err);
result.status = "訂單完成失敗。"
result.err = "伺服器錯誤,請稍後在試!"
reject(result);
return;
}
})
// 寄送Email通知
const memberData = await getMemberData(memberID);
const mailOptions = {
from: `"企鵝購物網" <${config.senderMail.user}>`, // 寄信
to: memberData.email, // 收信
subject: memberData.name + '您好,您所購買的訂單已經完成。', // 主旨
html: `<p>Hi, ${memberData.name} </p>` + `<br>` + `<br>` + `<span>感謝您訂購<b>企鵝購物網</b>的商品,歡迎下次再來!</span>` // 內文
}
transporter.sendMail(mailOptions, (err, info) => {
if (err) {
return console.log(err);
}
console.log('Message %s sent: %s', info.messageId, info.response);
})
result.status = "訂單編號:" + orderID + " 付款已完成,謝謝您使用該服務!詳細的訂單資訊已寄送至 " + memberData.email;
resolve(result);
}
})
}
註記:撰寫email的內文部分是用HTML語法來進行編譯,讀者可以在這部分花點巧思來設計內文的內容。
接續再將該model
匯入到controllers
> order
資料夾的modify_controller.js
檔案中:
putProductComplete(req, res, next) {
const token = req.headers['token'];
//確定token是否有輸入
if (check.checkNull(token) === true) {
res.json({
err: "請輸入token!"
})
} else if (check.checkNull(token) === false) {
verify(token).then(tokenResult => {
if (tokenResult === false) {
res.json({
result: {
status: "token錯誤。",
err: "請重新登入。"
}
})
} else {
const memberID = tokenResult;
const orderID = req.body.orderID;
orderComplete(orderID, memberID).then(result => {
res.json({
result: result
})
}, (err) => {
res.json({
result: err
})
})
}
})
}
}
最後,給予該API一個URL。這部份到routes
資料夾的order.js
檔案中寫入:
var express = require('express');
var router = express.Router();
const OrderGetMethod = require('../controllers/order/get_controller');
const OrderModifyMethod = require('../controllers/order/modify_controller');
orderGetMethod = new OrderGetMethod();
orderModifyMethod = new OrderModifyMethod();
// 取得全部訂單資料
router.get('/order', orderGetMethod.getAllOrder);
// 取得單一顧客的訂單資料
router.get('/order/member', orderGetMethod.getOneOrder);
// 訂整筆訂單
router.post('/order', orderModifyMethod.postOrderAllProduct);
// 更改單筆訂單資料
router.put('/order', orderModifyMethod.updateOrderProduct);
// 刪除訂單資料
router.delete('/order', orderModifyMethod.deleteOrderProduct);
// 訂單筆訂單
router.post('/order/addoneproduct', orderModifyMethod.postOrderOneProduct);
// 訂單已完成
router.put('/order/complete', orderModifyMethod.putProductComplete);
module.exports = router;
在進行測試前,我們先說明這次的測試目標。
目前的product
的table中,剩下的商品有這些。我們先用之前寫好的訂購功能來分別將商品2跟商品3的商品分別買2個跟1個。
之後到MySQL
資料庫裡面去看order_list
的結果。
發現到該筆訂單的編號是1,接續我們就使用Postman來進行測試,
HTTP method: PUT
HTTP url : localhost:3000/order/complete
Body中選擇x-www-form-urlencoded
headers
body
其結果:
之後,我們再到資料庫那邊做進一步的確認:
發現order_list
的table欄位is_complete
有成功變成1,也就是已經完成訂單的動作。以及,product
的table欄位quanaity
其商品2及商品3都有各扣2個及1個。
接續到email的部分,發現也有成功接收到該email。
註記:email部分的測試,讀者記得把收跟發的email都用成有效的email。順帶一提,最快的測試方式就是自己寄給自己。
商品系統-完整的code
商品系統的實作就此結束,不知道讀者的收穫如何?其實,不管是先前的會員系統或是目前的商品系統。在實務的商業邏輯部分,並沒有像筆者寫得這麼單純,若要真的像那些正在上線運作的系統,目前的功能是不健全的。但若是讀者要拿來練習基本
的Node.js,那筆者想應該是綽綽有餘且滿有趣的。
筆者在該部分還有匯出Excel功能
及Unit Test
的部分還未講解,但若寫下去會超過30篇的篇幅。但別擔心,筆者之後會再額外補上。接續,我們即將來到下個章節關於Server建置
的架設部分。